support JupyterHub access scopes#863
Conversation
- disabled by default for backward-compatibility - opt-in by setting jupyterhub_service_name - prefix service usernames so they don't collide with users
9235074 to
702dc63
Compare
|
Also possibly relevant, since I don't know how dask-labextension works: Does the lab extension use the PageConfig.token to make requests directly to the Gateway, or does it make requests only to a server extension, which then uses $JUPYTERHUB_API_TOKEN? If it uses the jupyterlab token, the service access permission would also need to be granted to |
|
Great to see this existing :) Given the general lack of activity in this project, I would suggest making this non-breaking to reduce potential support burden on the existing limited set of maintainers |
|
Also I believe the labextension only talks to the serverextension so you should be clear there |
|
I would definitely appreciate it being non-breaking. Feel free to ping me directly for review when it's ready. |
|
The code was already entirely unchanged in defaults, so I stuck with that. There's a warning, because the defaults are insecure. This is now documented and tested and extended to the dask-gateway chart, all with defaults unchanged. I'm not sure insecure by default is the best thing, but given the activity level of this repo, it makes sense. Maybe in daskhub the defaults can be changed to be more secure, since it can tie it all together to still grant all users access. |
|
cc @jacobtomlinson for review |
consideRatio
left a comment
There was a problem hiding this comment.
Beautiful @minrk! Thank you for working this!!
I look for clarity about the one review comment I made, but I figure we can proceed with a merge besides that.
| # avoid collisions between user names and service names | ||
| # 'kind' may be 'user' or 'service' | ||
| username = data["name"] | ||
| if data["kind"] != "user" or username.startswith(("user:", "service:")): | ||
| # avoid collision without changing the name for users | ||
| username = f"{data['kind']}:{username}" |
There was a problem hiding this comment.
Should this code have or not rather than or in the if statement? I didn't understand this code block.
The dask-gateway-server user's username will be changed if it starts with user: or service:, which perhaps may or may not ever be allowed though - I'm not sure. It contradicts the comment about not changing the username. Perhaps it should include "except in the edge case the name is starting with "user:" or "service:".
There was a problem hiding this comment.
I've updated the comment and simplified the condition to clarify.
This is to avoid collisions and also avoid changes for the ~99% of cases where it's just users accessing the service. JupyterHub doesn't put limits on usernames, so e.g. a user can technically have the name service:name (unlikely because most identity providers don't allow this). This ensures that a user is never misrepresented as service:name in this situation, they would be user:service:name.
gateway doesn't really use this for anything, so it's not that important. But this at least guarantees there is no collision. You can parse the result with:
if ":" not in username:
kind = "user"
name = username
else:
kind, _, name = username.partition(":")and it will always be correct, even for names containing :.
|
(EDIT: flakey, resolved) The Py 3.14 / go 1.26 test passed twice in main branch, but it failed twice in this PR =/ Maybe rebase on main and see if it changes anything as a first step. |
0be7553 to
2d4a10c
Compare
Most details can be found in #829 but this would allow standard and more fine-grained control of access to the gateway service for tokens. The default is currently unchanged, for maximum compatibility,. though I would consider it insecure.
To enable this with users being able to access this service by default from their server (minimal change from current default capabilities), a deployment needs to:
c.JupyterHubAuthenticator.jupyterhub_service_name, the environment variableJUPYTERHUB_SERVICE_NAME, or via the Helm chart'sauth.jupyterhub.jupyterhubServiceName. Usually to"dask-gateway".access:services!service=dask-gatewayscope via the defaultuserrole (or a different role, if it should be less than all users, which is part of the point)Spawner.server_token_scopes(or theserverrole, prior to JupyterHub 4.0)The main idea of this is it enables both:
since as it is now, JupyterHubAuth grants any token provided to any jupyterhub service full access to the dask gateway.
TODO:
closes #829